// License: GPL. For details, see LICENSE file.
package de.unistuttgart.iev.osm.bboxchooser;

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.NumberFormat;
import java.text.ParseException;

import javax.swing.Box;
import javax.swing.JFormattedTextField;
import javax.swing.JPanel;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

/**
 * Displays a bounding box using four editable text fields.
 * 
 * @author Igor Podolskiy
 */
public class BBoxDisplay extends JPanel {

	public static final String BBOX_PROP = BBoxDisplay.class.getName()
			+ ".bbox"; //$NON-NLS-1$

	private enum BBoxField {
		LEFT, TOP, RIGHT, BOTTOM
	}

	private class BBoxUpdater implements PropertyChangeListener {

		private BBoxField field;

		public BBoxUpdater(BBoxField field) {
			this.field = field;
		}

		@Override
		public void propertyChange(PropertyChangeEvent evt) {
			if (!evt.getPropertyName().equals("value")) {
				return;
			}
			JFormattedTextField source = (JFormattedTextField) evt.getSource();
			Object value = source.getValue();
			if (value == null) {
				return;
			}
			if (value instanceof Long) {
				value = ((Long) value).doubleValue();
			}
			updateBoundingBox(field, (Double) value);
		}
	}

	private class FieldListener implements DocumentListener {

		private JFormattedTextField host;

		public FieldListener(JFormattedTextField host, BBoxField field) {
			super();
			this.host = host;
		}

		@Override
		public void removeUpdate(DocumentEvent e) {
			commitEdit();
		}

		private void commitEdit() {
			try {
				host.commitEdit();
			} catch (ParseException ex) {
			}
		}

		@Override
		public void insertUpdate(DocumentEvent e) {
			commitEdit();
		}

		@Override
		public void changedUpdate(DocumentEvent e) {
			commitEdit();
		}
	}

	private static final long serialVersionUID = 1L;

	private JFormattedTextField leftField;
	private JFormattedTextField topField;
	private JFormattedTextField rightField;
	private JFormattedTextField bottomField;

	private Bounds bbox;

	public BBoxDisplay() {
		createGUI();
		updateFields();
	}

	private void createGUI() {
		leftField = new JFormattedTextField(NumberFormat.getInstance());
		leftField.addPropertyChangeListener(new BBoxUpdater(BBoxField.LEFT));
		leftField.getDocument().addDocumentListener(
				new FieldListener(leftField, BBoxField.LEFT));
		leftField.setToolTipText(Messages.getString("BBoxDisplay.BoundLeft")); //$NON-NLS-1$
		leftField.setColumns(6);

		rightField = new JFormattedTextField(NumberFormat.getInstance());
		rightField.addPropertyChangeListener(new BBoxUpdater(BBoxField.RIGHT));
		rightField.getDocument().addDocumentListener(
				new FieldListener(rightField, BBoxField.RIGHT));
		rightField.setToolTipText(Messages.getString("BBoxDisplay.BoundRight")); //$NON-NLS-1$
		rightField.setColumns(6);

		topField = new JFormattedTextField(NumberFormat.getInstance());
		topField.addPropertyChangeListener(new BBoxUpdater(BBoxField.TOP));
		topField.getDocument().addDocumentListener(
				new FieldListener(topField, BBoxField.TOP));
		topField.setToolTipText(Messages.getString("BBoxDisplay.BoundTop")); //$NON-NLS-1$
		topField.setColumns(6);

		bottomField = new JFormattedTextField(NumberFormat.getInstance());
		bottomField
				.addPropertyChangeListener(new BBoxUpdater(BBoxField.BOTTOM));
		bottomField.getDocument().addDocumentListener(
				new FieldListener(bottomField, BBoxField.BOTTOM));
		bottomField.setToolTipText(Messages
				.getString("BBoxDisplay.BoundBottom")); //$NON-NLS-1$
		bottomField.setColumns(6);

		JPanel tfPanel = new JPanel();

		GridLayout layout = new GridLayout(3, 3);
		tfPanel.setLayout(layout);

		tfPanel.add(Box.createGlue());
		tfPanel.add(topField, BorderLayout.NORTH);
		tfPanel.add(Box.createGlue());

		tfPanel.add(leftField, BorderLayout.WEST);
		tfPanel.add(Box.createGlue());
		tfPanel.add(rightField, BorderLayout.EAST);

		tfPanel.add(Box.createGlue());
		tfPanel.add(bottomField, BorderLayout.SOUTH);
		tfPanel.add(Box.createGlue());

		Box box = Box.createHorizontalBox();
		box.add(Box.createHorizontalGlue());
		box.add(tfPanel);
		box.add(Box.createHorizontalGlue());

		add(box);
	}

	private void setBoundingBoxInternal(Bounds bbox) {
		Bounds old = this.bbox;
		if (bbox.equals(this.bbox)) {
			return;
		}
		this.bbox = bbox;
		firePropertyChange(BBOX_PROP, old, this.bbox);
	}

	public void setBoundingBox(Bounds bbox) {
		setBoundingBoxInternal(bbox);
		updateFields();
	}

	private void updateBoundingBox(BBoxField field, double newValue) {
		Bounds newBbox = null;
		Bounds oldBbox = getBoundingBox();
		if (oldBbox == null) {
			return;
		}

		switch (field) {
		case LEFT:
			newBbox = new Bounds(oldBbox.getMin().lat(), newValue, oldBbox
					.getMax().lat(), oldBbox.getMax().lon());
			break;
		case TOP:
			newBbox = new Bounds(oldBbox.getMin().lat(),
					oldBbox.getMin().lon(), newValue, oldBbox.getMax().lon());
			break;
		case RIGHT:
			newBbox = new Bounds(oldBbox.getMin().lat(),
					oldBbox.getMin().lon(), oldBbox.getMax().lat(), newValue);
			break;
		case BOTTOM:
			newBbox = new Bounds(newValue, oldBbox.getMin().lon(), oldBbox
					.getMax().lat(), oldBbox.getMax().lon());
			break;
		}
		setBoundingBoxInternal(newBbox);
	}

	private void updateFields() {
		if (bbox == null) {
			leftField.setText(""); //$NON-NLS-1$
			rightField.setText(""); //$NON-NLS-1$
			topField.setText(""); //$NON-NLS-1$
			bottomField.setText(""); //$NON-NLS-1$
		} else {
			leftField.setValue(bbox.getMin().lon());
			bottomField.setValue(bbox.getMin().lat());
			rightField.setValue(bbox.getMax().lon());
			topField.setValue(bbox.getMax().lat());
		}
	}

	public Bounds getBoundingBox() {
		return bbox;
	}
}
